package org.texteasy;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.texteasy.util.EmptyService;

import android.accounts.Account;
import android.app.Activity;
import android.app.ExpandableListActivity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.Settings;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;

public class DisplayGroupsActivity extends ExpandableListActivity implements OnClickListener{

	public static final int GROUPS_COLUMN_ACCOUNT_NAME = 0;
    public static final int GROUPS_COLUMN_ACCOUNT_TYPE = 1;
    
    public static final String TEXT_EASY_GROUP_SELECTIONS = "TEXT_EASY_GROUP_SELECTIONS";
	
    public static final String GROUP_SELECTION_ACCOUNT_NAME = "ACCOUNT_NAME";
    public static final String GROUP_SELECTION_ACCOUNT_TYPE = "ACCOUNT_TYPE";
    public static final String GROUP_SELECTION_GROUP_ID = "GROUP_ID";
    
    private DisplayAdapter displayAdapter;
    private TextEasyDBAdapter dbAdaptor;
    private boolean savePrefs = false;
    
	private static final String[] groupsProjection = new String[]{Groups._ID, Groups.SYSTEM_ID, Groups.TITLE, Groups.GROUP_VISIBLE};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		this.dbAdaptor = new TextEasyDBAdapter(this);
        this.dbAdaptor.open();
		
		setContentView(R.layout.display_groups);
		
		//expandableListView.setOnItemClickListener(this);
		findViewById(R.id.btn_done).setOnClickListener(this);
        findViewById(R.id.btn_discard).setOnClickListener(this);
        
        // Start background query to find group details
        new QueryGroupsTask(this).execute();
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		
		this.dbAdaptor.close();
		
		if(savePrefs){
			JSONArray groupSelections = new JSONArray();
			if(displayAdapter != null && displayAdapter.mAccounts != null){
				for(AccountDisplay accountDisplay: displayAdapter.mAccounts){
					for(GroupDelta group: accountDisplay.groups){
						if(group.getVisible()){
							Long groupId= group.getGroupId();
							JSONObject groupSelection = new JSONObject();
							try{
								groupSelection.put(GROUP_SELECTION_ACCOUNT_NAME, 
									accountDisplay.account.getName());
								groupSelection.put(GROUP_SELECTION_ACCOUNT_TYPE, 
									accountDisplay.account.getType());
								if(groupId == null){
									groupSelection.put(GROUP_SELECTION_GROUP_ID, 
											"null");
								}else{
									groupSelection.put(GROUP_SELECTION_GROUP_ID, 
										groupId.toString());
								}
								groupSelections.put(groupSelection);
							}catch(JSONException ex){
								throw new RuntimeException(ex);
							}
						}
					}
				}
			}
			
			SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
			Editor editor = prefs.edit();
			editor.putString(TEXT_EASY_GROUP_SELECTIONS, groupSelections.toString());
			editor.commit();
		}
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		if(this.dbAdaptor.isOpen()){
			this.dbAdaptor.close();
		}
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		
		this.dbAdaptor.open();

	}
	
	@Override
	public void onClick(View v) {
		
		switch(v.getId()){
			case R.id.btn_done: {
				this.doSaveAction();
				break;
			}
			case R.id.btn_discard: {
				this.finish();
				break;
			}
		}
	}
	
	private void doSaveAction(){
		
		if(displayAdapter == null){
			return;
		}
		savePrefs = true;
		new UpdateTask(this).execute(displayAdapter.mAccounts);
	}
	
	public void setListAdapter(DisplayAdapter adapter) {
		displayAdapter = adapter; 
		super.setListAdapter(adapter);
	}
	
	@Override
	public boolean onChildClick(ExpandableListView parent, View view,
			int groupPosition, int childPosition, long id) {
		
		final CheckBox checkbox = (CheckBox)view.findViewById(android.R.id.checkbox);
    	
    	final AccountDisplay account = (AccountDisplay)displayAdapter.getGroup(groupPosition);
    	final GroupDelta child = (GroupDelta)displayAdapter.getChild(groupPosition, childPosition);
    	if (child != null) {
            checkbox.toggle();
            child.setVisible(checkbox.isChecked());
        } /*else {
            // Open context menu for bringing back unsynced
            this.openContextMenu(view);
        }*/
    	return true;
	}
	
	/**
     * Background operation to build set of {@link AccountDisplay} for each
     * Account that provides groups.
     */
    private static class QueryGroupsTask extends
            WeakAsyncTask<Void, Void, AccountSet, DisplayGroupsActivity> {
    	
    	
    	public QueryGroupsTask(DisplayGroupsActivity target) {
            super(target);
        }
   	
    	@Override
    	protected AccountSet doInBackground(DisplayGroupsActivity target,
    			Void... params) {
    		
    		final Context context = target;
    		final ContentResolver resolver = context.getContentResolver();
    		
    		final AccountSet accountsSet = new AccountSet();
    		
    		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    		HashSet<String> selectedGroups = new HashSet<String>();
    		boolean useSystemDefaults = false;
    		try{
    			
    			String temp = prefs.getString(TEXT_EASY_GROUP_SELECTIONS, null);
    			if(temp != null){
		    		JSONArray textEasyGroupSelections= new JSONArray(
						prefs.getString(TEXT_EASY_GROUP_SELECTIONS, "[]"));
		    		
		    		for(int i=0; i < textEasyGroupSelections.length(); i++){
		    			JSONObject groupSelection = textEasyGroupSelections.getJSONObject(i);
		    			selectedGroups.add(groupSelection.getString(GROUP_SELECTION_ACCOUNT_NAME) + 
	    					"|" + groupSelection.getString(GROUP_SELECTION_ACCOUNT_TYPE) + 
	    					"|" + groupSelection.getString(GROUP_SELECTION_GROUP_ID));
		    		}
	    		}else{
	    			useSystemDefaults = true;
	    		}
	    		
    		}catch(JSONException ex){
    			throw new RuntimeException(ex);
    		}
    		
    		for(org.texteasy.Account account: org.texteasy.Account.getAccounts(context)){
    			accountsSet.add(new AccountDisplay(context, resolver, account,selectedGroups, useSystemDefaults));
    		}
    		
    		return accountsSet;
    	}
    	
    	@Override
    	protected void onPostExecute(DisplayGroupsActivity target,
    			AccountSet result) {
    		
    		// Build adapter to show available groups
            final Context context = target;
            final DisplayAdapter adapter = new DisplayAdapter(context, result);
            target.setListAdapter(adapter);
    		
    	}
    	
    	
    	
    }
	
    /**
     * {@link Comparator} to sort by {@link Groups#_ID}.
     */
    private static Comparator<GroupDelta> sIdComparator = new Comparator<GroupDelta>() {
        public int compare(GroupDelta object1, GroupDelta object2) {
            return object1.getViewId() - object2.getViewId();
        }
    };
    
    /**
     * Set of all {@link AccountDisplay} entries, one for each source.
     */
    protected static class AccountSet extends ArrayList<AccountDisplay> {
		private static final long serialVersionUID = 1314772198831590801L;
    }
    
    protected static class AccountDisplay{
    	
    	public final org.texteasy.Account account;
    	
    	public final GroupDelta mUngrouped;
        public final ArrayList<GroupDelta> groups = new ArrayList<GroupDelta>();
    	
    	public AccountDisplay(Context context, ContentResolver resolver, org.texteasy.Account account,
			HashSet<String> selectedGroups, boolean useSystemDefaults){
    		
    		this.account = account;
    		
    		boolean hasGroups = false;
    		
    		final Uri groupsUri = Groups.CONTENT_URI.buildUpon().
    			appendQueryParameter(Groups.ACCOUNT_NAME, account.getName()).
    			appendQueryParameter(Groups.ACCOUNT_TYPE, account.getType()).build();
    		
    		Cursor groupsCursor = null; 
    		
    		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    		
    		try{
    			groupsCursor = resolver.query(groupsUri, groupsProjection, null, null, Groups.TITLE + " DESC");
    			
	    		while(groupsCursor.moveToNext()){
	    			
	    			final String systemId = groupsCursor.getString(BulkMessenger.GROUPS_COLUMN_INDEX_SYSTEM_ID);
	    			final long groupId = groupsCursor.getLong(BulkMessenger.GROUPS_COLUMN_INDEX_ID);
	    			final String title = groupsCursor.getString(BulkMessenger.GROUPS_COLUMN_INDEX_NAME);
	    			boolean visible = groupsCursor.getInt(BulkMessenger.GROUPS_COLUMN_GROUP_VISIBLE)!= 0;
	    			
	    			if(!useSystemDefaults && selectedGroups.contains(account.getName() + 
	    					"|" + account.getType() + "|" + groupId)){
	    				visible = true;
	    			}else if(!useSystemDefaults){
	    				visible = false;
	    			}
	    			
		            final GroupDelta group = GroupDelta.getInstance(systemId, groupId, title, visible, 
		            		account.getName(), account.getType());
		            addGroup(group);
		            hasGroups = true;
	    		}
    		}finally{
    			if(groupsCursor != null){
    				groupsCursor.close();
    			}
    		}
    		
    		// Create single entry handling ungrouped status
    		boolean visible = false;
    		if(selectedGroups.contains(account.getName() + "|" + account.getType() + "|null")){
    			visible = true;
    		}
    		
            mUngrouped = GroupDelta.getInstance(resolver, account.getName(), account.getType(), 
        		hasGroups, visible, !useSystemDefaults);
            addGroup(mUngrouped);
    	}
    	
        /**
         * Add the given {@link GroupDelta} internally, filing based on its
         * {@link GroupDelta#getShouldSync()} status.
         */
        private void addGroup(GroupDelta group) {
            groups.add(group);
        }
    }
    
    private static final int DEFAULT_VISIBLE = 0;
    
    /**
     * Entry holding any changes to {@link Groups} or {@link Settings} rows,
     * such as {@link Groups#SHOULD_SYNC} or {@link Groups#GROUP_VISIBLE}.
     */
    protected static class GroupDelta {
    	
    	private boolean mUngrouped = false;
        private boolean mAccountHasGroups;
        private Long groupId;
        private String systemId;
        private String title;
        private boolean visible = false;
        
        private GroupDelta(boolean ungrouped, boolean accountHasGroups,String systemId, 
        		Long groupId, String title,boolean visible) {
            super();
            
            this.mUngrouped = ungrouped;
            this.mAccountHasGroups =accountHasGroups;
            this.systemId = systemId;
            this.groupId = groupId;
            this.title = title;
            this.visible = visible;
        }
        
        /**
         * Build {@link GroupDelta} from the {@link Settings} row for the given
         * {@link Settings#ACCOUNT_NAME} and {@link Settings#ACCOUNT_TYPE}.
         */
        public static GroupDelta getInstance(ContentResolver resolver, String accountName,
                String accountType, boolean accountHasGroups, boolean visible, boolean useTextEasyPrefs) {
        	
        	final Uri settingsUri = Settings.CONTENT_URI.buildUpon()
		            .appendQueryParameter(Settings.ACCOUNT_NAME, accountName)
		            .appendQueryParameter(Settings.ACCOUNT_TYPE, accountType).build();
		    final Cursor cursor = resolver.query(settingsUri, new String[] {
		            Settings.SHOULD_SYNC, Settings.UNGROUPED_VISIBLE
		    		}, null, null, null);
        	
		    try{
                if (cursor != null && cursor.moveToFirst()) {
                    // Read existing values when present
                	int ungroupedVisible = cursor.getInt(1);
                	
                	if(useTextEasyPrefs && visible){
                		ungroupedVisible = 1;
                	}else if(useTextEasyPrefs){
                		ungroupedVisible = 0;
                	}else{
                		visible = ungroupedVisible != 0;
                	}
                	
                	return new GroupDelta(true, accountHasGroups,null, null, null, visible);
                } else {
                    // Nothing found, so treat as create
                    return new GroupDelta(true, accountHasGroups,null, null, null, visible);
                }
		    }finally {
                if (cursor != null) cursor.close();
            }
        }
        
        /*protected GroupDelta setUngrouped(boolean accountHasGroups) {
            mUngrouped = true;
            mAccountHasGroups = accountHasGroups;
            return this;
        }*/
        
        public void setVisible(boolean visible) {
    		this.visible = visible;	
        }
        
        public static GroupDelta getInstance(String systemId, long groupId, 
    		String title, boolean visible, String accountName, String accountType) {
        	
        	final GroupDelta entry = new GroupDelta(false, true,systemId, 
    			groupId, title, visible);
            return entry;
        }
        
        public boolean getVisible() {
        	
        	return visible;
        }
        
        public CharSequence getTitle(Context context) {
        	
        	if(title == null){
        		if(mAccountHasGroups){
        			return context.getText(R.string.display_ungrouped);
        		}else{
        			return context.getText(R.string.display_all_contacts);
        		}
        	}else{
        		return title;
        	}
        }

		public Long getGroupId() {
			return groupId;
		}
		
		/**
         * Return a valid integer value suitable for {@link View#setId(int)}.
         */
        public int getViewId() {
            final Long id = this.getGroupId();
            return (id == null) ? View.NO_ID : id.intValue();
        }

    }
 
    /**
     * {@link ExpandableListAdapter} that shows {@link GroupDelta} settings,
     * grouped by {@link Account} source. Shows footer row when any groups are
     * unsynced, as determined through {@link AccountDisplay#mUnsyncedGroups}.
     */
    protected static class DisplayAdapter extends BaseExpandableListAdapter{
        private Context mContext;
        private LayoutInflater mInflater;

        private AccountSet mAccounts;

        public DisplayAdapter(Context context, AccountSet accounts) {
            mContext = context;
            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mAccounts = accounts;
        }

        /** {@inheritDoc} */
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.display_child, parent, false);
            }

            final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
            final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
            final CheckBox checkbox = (CheckBox)convertView.findViewById(android.R.id.checkbox);

            final AccountDisplay account = mAccounts.get(groupPosition);
            final GroupDelta child = (GroupDelta)this.getChild(groupPosition, childPosition);
            if (child != null) {
                // Handle normal group, with title and checkbox
                final boolean groupVisible = child.getVisible();
                checkbox.setVisibility(View.VISIBLE);
                checkbox.setChecked(groupVisible);

                final CharSequence groupTitle = child.getTitle(mContext);
                text1.setText(groupTitle);
                text2.setVisibility(View.GONE);
            } else {
                // When unknown child, this is "more" footer view
                checkbox.setVisibility(View.GONE);
                text1.setText(R.string.display_more_groups);
                text2.setVisibility(View.GONE);
            }

            return convertView;
        }

        /** {@inheritDoc} */
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
                ViewGroup parent) {
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.display_group, parent, false);
            }

            final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
            final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);

            final AccountDisplay account = (AccountDisplay)this.getGroup(groupPosition);
            
            text1.setText(account.account.getName());
            text2.setText(account.account.getTypeLabel());
            text2.setVisibility(account.account.getName() == null ? View.GONE : View.VISIBLE);
            
            return convertView;
        }

        /** {@inheritDoc} */
        public Object getChild(int groupPosition, int childPosition) {
            final AccountDisplay account = mAccounts.get(groupPosition);
            final boolean validChild = childPosition >= 0
                    && childPosition < account.groups.size();
            if (validChild) {
                return account.groups.get(childPosition);
            } else {
                return null;
            }
        }

        /** {@inheritDoc} */
        public long getChildId(int groupPosition, int childPosition) {
            final GroupDelta child = (GroupDelta)getChild(groupPosition, childPosition);
            if (child != null) {
                final Long childId = child.getGroupId();
                return childId != null ? childId : Long.MIN_VALUE;
            } else {
                return Long.MIN_VALUE;
            }
        }

        /** {@inheritDoc} */
        public int getChildrenCount(int groupPosition) {
            // Count is any synced groups, plus possible footer
            final AccountDisplay account = mAccounts.get(groupPosition);
            return account.groups.size();
        }

        /** {@inheritDoc} */
        public Object getGroup(int groupPosition) {
            return mAccounts.get(groupPosition);
        }

        /** {@inheritDoc} */
        public int getGroupCount() {
            return mAccounts.size();
        }

        /** {@inheritDoc} */
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        /** {@inheritDoc} */
        public boolean hasStableIds() {
            return true;
        }

        /** {@inheritDoc} */
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return true;
        }
    }

    /**
     * Background task that persists changes to {@link Groups#GROUP_VISIBLE},
     * showing spinner dialog to user while updating.
     */
    public static class UpdateTask extends
            WeakAsyncTask<AccountSet, Void, Void, Activity> {
        private WeakReference<ProgressDialog> mProgress;

        public UpdateTask(Activity target) {
            super(target);
        }
        
        /** {@inheritDoc} */
        @Override
        protected void onPreExecute(Activity target) {
            final Context context = target;

            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(context, null,
                    context.getText(R.string.savingDisplayGroups)));

            // Before starting this task, start an empty service to protect our
            // process from being reclaimed by the system.
            context.startService(new Intent(context, EmptyService.class));
        }
        
		@Override
		protected Void doInBackground(Activity target, AccountSet... params) {
			final Context context = target;
			final ContentResolver contentResolver = context.getContentResolver();
			final AccountSet set = params[0];
			
			ArrayList<Pair<String,String>> ungroupedSelections = new ArrayList<Pair<String,String>>();
			ArrayList<Long> selectedGroupIds = new ArrayList<Long>();
			
			for(AccountDisplay accountDisplay: set){
				for(GroupDelta group: accountDisplay.groups){
					if(group.getVisible()){
						Long groupId= group.getGroupId();
						if(groupId != null){
							selectedGroupIds.add(groupId);
						}else{
							ungroupedSelections.add(Pair.create(accountDisplay.account.getName(), 
								accountDisplay.account.getType()));
						}
					}
				}
			}
			
			SynchronizeGroupsAndContacts.synchronizeGroupsAndContacts(contentResolver,selectedGroupIds,
					ungroupedSelections, ((DisplayGroupsActivity)target).dbAdaptor);
			
			return null;
		}
        
		/** {@inheritDoc} */
        @Override
        protected void onPostExecute(Activity target, Void result) {
            final Context context = target;

            final ProgressDialog dialog = mProgress.get();
            if (dialog != null) dialog.dismiss();

            target.finish();

            // Stop the service that was protecting us
            context.stopService(new Intent(context, EmptyService.class));
        }
    }
    
}
